/* command.c */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include "command.h"
#include "vie.h"
#include "undo.h"
#include "search.h"
#include "ui.h"
#include "buffer.h"
#include "window.h"
#include "fileio.h" 
#include "clipboard.h"

#define OK 0
#define TRUE 1
#define FALSE 0

cmdmap_t * cmd_cmdmap = NULL;
cmdmap_t * ex_cmdmap = NULL;

//clipboard
clipboard_t clipboard;

int cmd_quit(char *, int);
int cmd_save(char *, int);
int cmd_save_and_quit(char *, int);
int cmd_delete_line(char *, int);
int cmd_copy_line(char *, int);
int cmd_paste(char *, int);
int cmd_append(char *, int);
int cmd_insert(char *, int);
int cmd_make_new_line(char *, int);
int cmd_ex_mode(char *, int);
int cmd_replace(char *, int);
int cmd_down(char *, int);
int cmd_up(char *, int);
int cmd_left(char *, int);
int cmd_right(char *, int);
int cmd_undo(char *, int);
int cmd_redo(char *, int);
int cmd_search_next(char *, int);
int cmd_search_prev(char *, int);
int cmd_search(char *, int);
int cmd_next_window(char *, int);
int cmd_prev_window(char *, int);
int cmd_substitute(char *, int);
int cmd_goto_line(char *, int);
int cmd_set_attribute(char *, int);
int cmd_delete_word_forward(char *, int);
int cmd_copy_word_forward(char *, int);

static int init_cmd_command(void);
static int init_ex_command(void);

static int position_after(buf_t *, int , buf_t *, int);
static int copy_lines(buf_t * start_buf, int num);
static int copy_string(buf_t * buf, int start, int end);
static int free_current_window(void);

int init_command(){
	//init clipboard
	clipboard.clip = NULL;
	//init commands
	init_cmd_command();
	init_ex_command();
	return OK;
}

static int init_cmd_command(){
	if(cmd_cmdmap)
		return 0;
	cmd_cmdmap = cmdmap_new();
	
	cmdmap_add(cmd_cmdmap, "ZZ", cmd_save_and_quit);
	cmdmap_add(cmd_cmdmap, ":", cmd_ex_mode);	
	cmdmap_add(cmd_cmdmap, "dd",cmd_delete_line);
	cmdmap_add(cmd_cmdmap, "dw",cmd_delete_word_forward);

	cmdmap_add(cmd_cmdmap, "yy",cmd_copy_line);
	cmdmap_add(cmd_cmdmap, "yw",cmd_copy_word_forward);

	cmdmap_add(cmd_cmdmap, "p",cmd_paste);
	
	cmdmap_add(cmd_cmdmap, "a",cmd_append);
	cmdmap_add(cmd_cmdmap, "i",cmd_insert);
	cmdmap_add(cmd_cmdmap, "I",cmd_insert);
	cmdmap_add(cmd_cmdmap, "o",cmd_make_new_line);
	cmdmap_add(cmd_cmdmap, "r",cmd_replace);
	cmdmap_add(cmd_cmdmap, "R",cmd_replace);

	cmdmap_add(cmd_cmdmap, "h",cmd_left);
	cmdmap_add(cmd_cmdmap, "j",cmd_up);
	cmdmap_add(cmd_cmdmap, "k",cmd_down);
	cmdmap_add(cmd_cmdmap, "l",cmd_right);
	
	cmdmap_add(cmd_cmdmap, "u",cmd_undo);
	cmdmap_add(cmd_cmdmap, ".",cmd_redo);
	
	cmdmap_add(cmd_cmdmap, "n",cmd_search_next);
	cmdmap_add(cmd_cmdmap, "N",cmd_search_prev);
	

	cmdmap_add(cmd_cmdmap, "w",cmd_next_window);
	cmdmap_add(cmd_cmdmap, "W",cmd_prev_window);

	cmdmap_add(cmd_cmdmap, "/",cmd_search);
	cmdmap_add(cmd_cmdmap, "?",cmd_search);
	
	cmdmap_add(cmd_cmdmap, "G",cmd_goto_line);	
	cmdmap_sort(cmd_cmdmap);
	return 0;
}

static int init_ex_command(){
	if(ex_cmdmap)
		return 0;
	ex_cmdmap = cmdmap_new();
	
	cmdmap_add(ex_cmdmap, "exit", cmd_quit);
	cmdmap_add(ex_cmdmap, "q", cmd_quit);
	
	cmdmap_add(ex_cmdmap, "w", cmd_save);
	
	cmdmap_add(ex_cmdmap, "deleteline", cmd_delete_line);
	cmdmap_add(ex_cmdmap, "yankline", cmd_copy_line);
	cmdmap_add(ex_cmdmap, "set", cmd_set_attribute);


	cmdmap_add(ex_cmdmap, "nw",cmd_next_window);
	cmdmap_add(ex_cmdmap, "pw",cmd_prev_window);

	cmdmap_add(ex_cmdmap, "undo",cmd_undo);
	cmdmap_add(ex_cmdmap, "redo",cmd_redo);
	
	cmdmap_add(ex_cmdmap, "next",cmd_search_next);
	cmdmap_add(ex_cmdmap, "prev",cmd_search_prev);
	
	cmdmap_add(ex_cmdmap, "?",cmd_search);
	cmdmap_add(ex_cmdmap, "/",cmd_search);
	cmdmap_add(ex_cmdmap, "%",cmd_substitute);

	cmdmap_sort(ex_cmdmap);
	return 0;
}

val_t cmd_command_find(char * cmd){
	return cmdmap_find(cmd_cmdmap, cmd);
}

val_t ex_command_find(char *cmd){
	return cmdmap_find(ex_cmdmap, cmd);
}

int end_command(void){
	cmdmap_free(cmd_cmdmap);
	cmdmap_free(ex_cmdmap);
	cmd_cmdmap = NULL;
	ex_cmdmap = NULL;
	return 0;
}

// command handlers
int cmd_quit(char * arg1, int arg2){
	if(!current_window->is_saved){	// if the file has been modified.
		if(arg2 == 1){	//force quit
			free_current_window();
			return OK;
		}else{
			state_area_show_format_error_str("File %s has been modified!", current_window->path);
			return -1;
		}
	}else{
		free_current_window();
		return OK;
	}
}

int cmd_save(char * arg1, int arg2){
	int code = 0;
	if(arg1 != NULL && strlen(arg1)!=0){
		code = file_save(arg1);
		if(OK == code){
			current_window->is_saved = TRUE;
			update_state_area_with_format_string_str("File %s has been saved", current_window->path);
		}else{
			state_area_show_format_error_str("Cannot save file %s!", current_window->path);
			return -1;
		}
	}else if(current_window->path != NULL){
		code = file_save(current_window->path);
		if(OK == code){
			current_window->is_saved = TRUE;
			update_state_area_with_format_string_str("File %s has been saved", current_window->path);
		}else{
			state_area_show_format_error_str("Cannot save file %s!", current_window->path);
			return -1;
		}
	}else{
		state_area_show_error("Error: File name not specified!");
		return -1;
	}
	return OK;
}

int cmd_save_and_quit(char *arg1, int arg2){
	if(OK == cmd_save(NULL, 0)){
		free_current_window();
	}else{
		return -1;
	}
	return OK;
}

int cmd_ex_mode(char *arg1, int arg2){
	switch_mode(MODE_EX,TRUE);
	return OK;
}

int cmd_delete_line(char * arg1, int arg2){
	int line_count = 0;
	buf_t * temp_buf;
	buf_t * buf_to_remove;
	if(arg2<=0)
		arg2 = 1;
	line_count = copy_lines(current_window->buf, arg2);
	if(line_count <=0){
		state_area_show_error("Error while deleting lines");
		update_screen();
		sleep(3);
		exit_vie(-1);
	}
	buf_start_cmds();
	buf_to_remove = current_window->buf;
	temp_buf = buf_next(buf_to_remove);
	for(; line_count>0; line_count --){
		buf_remove_buf(buf_to_remove);
		buf_to_remove = temp_buf;
		if(temp_buf != NULL)
			temp_buf = buf_next(buf_to_remove);
		else
			break;
	}
	if(current_window->buf == NULL){
		buf_create_buf(0);
	}
	buf_finish_cmds();
	//adjust the cursor.
	if(temp_buf != NULL){
		if(buf_prev(temp_buf) != NULL)
			current_window->buf = buf_prev(temp_buf);
		else
			current_window->buf = temp_buf;
	}else{
		buf_to_tail();
	}
	current_window->buf->last_modified_pos = 0;
	roll_screen();
	return OK;
}

int cmd_copy_line(char * arg1, int arg2){
	static char msg_buffer[256];
	int line_count = 0;
	if(arg2<=0)
		arg2 = 1;
	if((line_count = copy_lines(current_window->buf, arg2))>0){
		sprintf(msg_buffer, " %d lines yanked.",line_count);
		update_state_area_with_string(msg_buffer);
	}else{
		state_area_show_error("Cannot yank lines!");
		return -1;
	}
	return OK;
}

int cmd_append(char * arg1, int arg2){
	switch_mode(MODE_INSERT,TRUE);
	return OK;
}

int cmd_insert(char * arg1, int arg2){
	switch_mode(MODE_INSERT,TRUE);
	return OK;
}

int cmd_replace(char * arg1, int arg2){
	switch_mode(MODE_INSERT,TRUE);
	return OK;
}

int cmd_down(char * arg1, int arg2){
	key_down();
	return OK;	
}

int cmd_up(char * arg1, int arg2){
	key_up();
	return OK;
}

int cmd_left(char * arg1, int arg2){
	key_left();
	return OK;
}

int cmd_right(char * arg1, int arg2){
	key_right();
	return OK;
}

int cmd_undo(char * arg1, int arg2){
	int code = undo();
	roll_screen();
	if(code == UNDO_OK){
		update_state_area_with_string("undo succeed. 1 change canceled.");
	}else{
		update_state_area_with_string("Already at the oldest change.");
	}
	return OK;
}

int cmd_redo(char * arg1, int arg2){
	int code = redo();
	roll_screen();
	if(code == UNDO_OK){
		update_state_area_with_string("redo succeed. 1 change reverted.");
	}else{
		update_state_area_with_string("Nothing to do.");
	}
	return OK;
}

int cmd_search_next(char * arg1, int arg2){
	find_result_t * result = find_next();
	if(result == NULL){	//Pattern Not Found
		state_area_show_error("Pattern Not Found!");
	}else{
		current_window->buf = result->buf;
		current_window->buf->last_modified_pos = result->match->rm_so;
		roll_screen();
	}
	return OK;
}

int cmd_search_prev(char * arg1, int arg2){
	return OK;
}

int cmd_search(char * arg1, int arg2){
	char buffer[256];
	find_finish();
	update_screen();
	if( 0 == state_area_get_string("/", buffer)){
		if( 0 == find_start(buffer, current_window->buf, viescr.cursor_pos.offset)){
			cmd_search_next(NULL, 0);
		}else{	//wrong regx
			state_area_show_format_error_str("Invalid Pattern: %s", buffer);
		}
		return OK;
	}else{
		return -1;
	}
}


int cmd_substitute(char *arg1, int arg2){
	char dest[256];
	char subs[256];
	char *ptr = NULL;
	char *ptr1 = NULL;
	int sub_count = 0;
	find_result_t *result = NULL;
	int len;
	buf_t * start_buf;
	int start_pos;
	if(arg1[0] != 's' || arg1[1] != '/'){
		state_area_show_error("Invalid command!");
		return -1;
	}
	len = strlen(arg1);
	dest[0]= subs[0]= '\0';
	//find the dest
	ptr = strstr(arg1+2, "/");
	if(ptr == NULL){
		state_area_show_error("Invalid command!");
		return -1;
	}
	strncpy(dest, arg1 + 2, ptr - arg1 -2);
	dest[ptr-arg1-2]='\0';
	//find the subs
	ptr1 = ptr +1;
	ptr = strstr(ptr1, "/");
	if(ptr == NULL){
		state_area_show_error("Invalid command!");
		return -1;
	}
	strncpy(subs, ptr1, ptr - ptr1);
	subs[ptr-ptr1 ] = '\0';
	/* start search and replace */
	start_buf = current_window->buf;
	start_pos = viescr.cursor_pos.offset;
	find_finish();
	find_start(dest, current_window->buf, viescr.cursor_pos.offset);
	while((result = find_next())!= NULL){
		if( position_after(start_buf, start_pos, result->buf, result->match->rm_so)){
				sub_count++;
				buf_replace(result->buf, result->match->rm_so, result->match->rm_eo, subs);
		current_window->buf = result->buf;
		current_window->buf->last_modified_pos = result->match->rm_so;
		}else{
			break;
		}
	}
	if(sub_count)
		roll_screen();
	//print messeage;
	sprintf(dest, "Replace done, %d places are replaced.",sub_count);
	update_state_area_with_string(dest);
	return OK;	
}

int cmd_next_window(char * arg1, int arg2){
	window_current_next();
	buf_to_head();
	viescr.start_buf = current_window->buf;
	viescr.cursor_pos.buf = current_window->buf;
	if(current_window->path == NULL ||0 == strlen(current_window->path)){
		update_state_area_with_string("FILE: unnamed");
	}else{
		char msg_buf[256];
		sprintf(msg_buf, "FILE: %s", current_window->path);
		update_state_area_with_string(msg_buf);
	}
	roll_screen();
	return OK;
}

int cmd_prev_window(char * arg1, int arg2){
	window_current_prev();
	buf_to_tail();
	viescr.start_buf = current_window->buf;
	viescr.cursor_pos.buf = current_window->buf;
	if(current_window->path == NULL || 0 == strlen(current_window->path)){
		update_state_area_with_string("FILE: unnamed");
	}else{
		char msg_buf[256];
		sprintf(msg_buf, "FILE: %s", current_window->path);
		update_state_area_with_string(msg_buf);
	}
	roll_screen();
	return OK;
}


int cmd_goto_line(char * arg1, int arg2){
	buf_t *itr;
	int line_count = 0;
	if(arg2 > 0){
		buf_to_head();
		for(itr = current_window->buf; itr != NULL; itr = buf_next(itr)){
			line_count ++;
			if(line_count == arg2)
				break;
		}
		if(itr == NULL){
			buf_to_tail();
			itr = current_window->buf;
		}
	}else{
		buf_to_tail();
		itr = current_window->buf;
	}

	current_window->buf = itr;
	current_window->buf->last_modified_pos = 0;
	roll_screen();
	return OK;
}


int cmd_paste(char *arg1, int arg2){
	int i;
	if(clipboard.clip == NULL)
		return -1;
	if(clipboard.clip->clip_type == CLIP_BUFFER){
		buf_start_cmds();
		for(i=0; i<clipboard.clip->buf_stack.buf_num;i++){
			buf_insert_buf(buf_copy_buf(clipboard.clip->buf_stack.stack[i]), current_window->buf);
		}
		buf_finish_cmds();
		update_state_area_with_string("Paste finished.");
	}else if(clipboard.clip->clip_type == CLIP_STRING){
		buf_inserts(current_window->buf,clipboard.clip->string, viescr.cursor_pos.offset);
		update_state_area_with_string("Paste finished.");	
	}
	roll_screen();
	return OK;
}

int cmd_make_new_line(char *arg1, int arg2){
	buf_create_buf(current_window->buf->len);
	roll_screen();
	switch_mode(MODE_INSERT, TRUE);
	return OK;
}

int cmd_set_attribute(char *arg1, int arg2){
	if(strcmp(arg1, "nu") == 0){
		if(VIE_ATTRIBUTE_NUMBER){
			VIE_ATTRIBUTE_NUMBER = 0;
			set_left_margin(0);
		}else{
			VIE_ATTRIBUTE_NUMBER = 1;
			VIE_LEFT_MARGIN = 0;
			set_left_margin(5);
		}
		roll_screen();
	}else if(strcmp(arg1, "autoindent") == 0){
		if(VIE_ATTRIBUTE_AUTOINDENT)
			VIE_ATTRIBUTE_AUTOINDENT= 0;
		else
			VIE_ATTRIBUTE_AUTOINDENT=1;
	}else if(strcmp(arg1, "hl") == 0){
		if(VIE_ATTRIBUTE_HIGHLIGHT)
			VIE_ATTRIBUTE_HIGHLIGHT= 0;
		else
			VIE_ATTRIBUTE_HIGHLIGHT=1;
	}
	return OK;
}


int cmd_delete_word_forward(char *arg1, int arg2){
	int i;
	int start = viescr.cursor_pos.offset;
	int end;
	for(i = start; i< current_window->buf->len;i++){
		if(!isalnum(current_window->buf->data[i]))
			break;
	}
	end = i;
	if(start == end)return OK;
	copy_string(current_window->buf, start, end);
	buf_delete(current_window->buf, start, end);
	return OK;
}

int cmd_copy_word_forward(char *arg1, int arg2){
	int i;
	int start = viescr.cursor_pos.offset;
	int end;
	for(i = start; i< current_window->buf->len;i++){
		if(current_window->buf->data[i]== ' ' || current_window->buf->data[i] == '\t')
			break;
	}
	end = i;
	if(start == end)return OK;
	copy_string(current_window->buf, start, end);
	return OK;
}

static int position_after(buf_t *buf1, int pos1, buf_t *buf2, int pos2 ){
	buf_t *itr = buf1;
	if(buf1 == buf2) return ( pos1 > pos2);
	for(itr = buf1; itr != NULL; itr = buf_next(itr)){
		if(itr == buf2){
			return TRUE;
		}
	}
	return FALSE;
}


static int copy_lines(buf_t * start_buf, int num){
	clip_t * clp = clip_new();
	bufstack_t bs;
	buf_t * itr;
	buf_t * temp_buf;
	int line_count = 0;
	
	if(start_buf == NULL)
		return 0;
	if(num<=0)return num;
	clp->clip_type = CLIP_BUFFER;
	//clear clipboard
	clipboard_clear(&clipboard);
	clipboard_add_clip(&clipboard, clp);
	//copy bufs
	allocate_buf_array( &bs , num );
	clp->buf_stack.buf_num = bs.buf_num;
	clp->buf_stack.stack = bs.stack;
	for(itr = start_buf; itr != NULL; itr = buf_next(itr)){
		temp_buf = buf_copy_buf(itr);
		if(temp_buf == NULL){
			fputs("Error occured while copying buffers",stderr);
			exit(-1);
		}
		clp->buf_stack.stack[line_count] = temp_buf;
		line_count++;
		if(line_count == num)
			return num;
	}
	clp->buf_stack.buf_num = line_count;
	return line_count;
}

static int copy_string(buf_t * buf, int start, int end){
	
	clip_t * clp = clip_new();
	char * str_ptr = NULL;

	if(start>= end)return -1;

	clp->clip_type = CLIP_STRING;
	//clear clipboard
	clipboard_clear(&clipboard);
	clipboard_add_clip(&clipboard, clp);
	//copy string
	str_ptr = (char *)malloc(sizeof(char)*(end - start+1));
	if(str_ptr == NULL) return -1;
	
	strncpy(str_ptr, buf->data+start, end - start);
	str_ptr[end - start] = '\0';
	clp->string = str_ptr;
	return OK;
}

static int free_current_window(){
	window_t *win = window_next(current_window);
	if(win == current_window){
		exit_vie(0);
		return OK;
	}else{
		win = current_window;
		cmd_next_window(NULL, 0);
		window_remove_window(win);
		return OK;	
	}
}
